#!/usr/bin/env python3
# coding=utf-8

import time
import threading

from motor.utils.timer import Timer, CyclicTimer


class TestTimer:
    """Test cases for Timer class"""
    
    def setup_method(self):
        """Setup for each test method"""
        self.timer = Timer()
        self.callback_called = False
        self.callback_args = None
        self.callback_kwargs = None
    
    def teardown_method(self):
        """Cleanup after each test method"""
        self.timer.stop()
    
    def callback_function(self, *args, **kwargs):
        """Test callback function"""
        self.callback_called = True
        self.callback_args = args
        self.callback_kwargs = kwargs
    
    def test_timer_initialization(self):
        """Test timer initialization with default values"""
        assert self.timer._timer is None
        assert self.timer._timeout is None
        assert self.timer._callback is None
        assert self.timer._args == ()
        assert self.timer._kwargs == {}
        assert not self.timer._is_running
    
    def test_timer_start_basic(self):
        """Test basic timer start functionality"""
        self.timer.start(self.callback_function, 0.01)
        
        # Check timer state immediately after start
        assert self.timer.is_running()
        assert self.timer._callback == self.callback_function
        assert self.timer._timeout == 0.01
        
        # Wait for timer to complete
        time.sleep(0.02)
        
        # Check that callback was called and timer is no longer running
        assert self.callback_called
        assert not self.timer.is_running()
    
    def test_timer_start_with_args(self):
        """Test timer start with positional and keyword arguments"""
        args = (1, 2, 3)
        kwargs = {'key1': 'value1', 'key2': 'value2'}
        
        self.timer.start(self.callback_function, 0.01, *args, **kwargs)
        
        time.sleep(0.02)
        assert self.callback_called
        assert self.callback_args == args
        assert self.callback_kwargs == kwargs
    
    def test_timer_stop(self):
        """Test timer stop functionality"""
        self.timer.start(self.callback_function, 0.05)
        assert self.timer.is_running()
        
        self.timer.stop()
        assert not self.timer.is_running()
        assert not self.callback_called  # Callback should not be called
    
    def test_timer_restart(self):
        """Test timer restart when already running"""
        self.timer.start(self.callback_function, 0.05)
        assert self.timer.is_running()
        
        # Start new timer with shorter timeout
        self.timer.start(self.callback_function, 0.01)
        assert self.timer.is_running()
        
        time.sleep(0.02)
        assert self.callback_called
    
    def test_timer_set_timeout_while_running(self):
        """Test setting timeout while timer is running"""
        self.timer.start(self.callback_function, 0.05)
        assert self.timer.is_running()
        
        # Change timeout to shorter duration
        self.timer.set_timeout(0.01)
        assert self.timer.is_running()
        
        time.sleep(0.02)
        assert self.callback_called
    
    def test_timer_set_timeout_while_stopped(self):
        """Test setting timeout while timer is stopped"""
        self.timer.set_timeout(0.01)
        assert self.timer._timeout == 0.01
        assert not self.timer.is_running()
    
    def test_timer_multiple_stops(self):
        """Test calling stop multiple times"""
        self.timer.start(self.callback_function, 0.01)
        self.timer.stop()
        self.timer.stop()  # Should not raise exception
        assert not self.timer.is_running()
    
    def test_timer_thread_safety(self):
        """Test timer thread safety with concurrent operations"""
        results = []
        
        def worker():
            for i in range(3):  # Further reduced iterations
                self.timer.start(self.callback_function, 0.001)  # Very short timeout
                time.sleep(0.0005)  # Reduced sleep
                self.timer.stop()
                results.append(i)
        
        threads = []
        for _ in range(2):  # Further reduced thread count
            thread = threading.Thread(target=worker)
            threads.append(thread)
            thread.start()
        
        for thread in threads:
            thread.join()
        
        assert len(results) == 6  # 2 threads * 3 iterations
    
    def test_timer_callback_exception(self):
        """Test timer behavior when callback raises exception"""
        def failing_callback():
            raise ValueError("Test exception")
        
        # Timer should not crash when callback raises exception
        self.timer.start(failing_callback, 0.01)
        time.sleep(0.02)
        
        # Timer should be stopped after callback execution
        assert not self.timer.is_running()
    
    def test_timer_zero_timeout(self):
        """Test timer with zero timeout"""
        self.timer.start(self.callback_function, 0)
        time.sleep(0.01)  # Small delay to allow callback execution
        assert self.callback_called


class TestCyclicTimer:
    """Test cases for CyclicTimer class"""
    
    def setup_method(self):
        """Setup for each test method"""
        self.timer = CyclicTimer()
        self.callback_count = 0
        self.callback_args = None
        self.callback_kwargs = None
    
    def teardown_method(self):
        """Cleanup after each test method"""
        self.timer.stop()
    
    def callback_function(self, *args, **kwargs):
        """Test callback function for cyclic timer"""
        self.callback_count += 1
        self.callback_args = args
        self.callback_kwargs = kwargs
    
    def test_cyclic_timer_initialization(self):
        """Test cyclic timer initialization"""
        assert not self.timer._is_cyclic
        assert not self.timer.is_cyclic()
        assert not self.timer.is_running()
    
    def test_cyclic_timer_start_basic(self):
        """Test basic cyclic timer start functionality"""
        self.timer.start_cyclic(self.callback_function, 0.01)
        
        assert self.timer.is_running()
        assert self.timer.is_cyclic()
        
        # Wait for multiple cycles
        time.sleep(0.05)
        assert self.callback_count >= 3  # Should have executed at least 3 times
    
    def test_cyclic_timer_start_with_args(self):
        """Test cyclic timer start with arguments"""
        args = (1, 2, 3)
        kwargs = {'key1': 'value1'}
        
        self.timer.start_cyclic(self.callback_function, 0.01, *args, **kwargs)
        
        time.sleep(0.05)
        assert self.callback_count >= 2
        assert self.callback_args == args
        assert self.callback_kwargs == kwargs
    
    def test_cyclic_timer_stop(self):
        """Test cyclic timer stop functionality"""
        self.timer.start_cyclic(self.callback_function, 0.01)
        assert self.timer.is_running()
        assert self.timer.is_cyclic()
        
        time.sleep(0.02)  # Let it run for one cycle
        initial_count = self.callback_count
        
        self.timer.stop()
        assert not self.timer.is_running()
        assert not self.timer.is_cyclic()
        
        # Wait a bit more to ensure it doesn't continue
        time.sleep(0.02)
        assert self.callback_count == initial_count
    
    def test_cyclic_timer_restart(self):
        """Test cyclic timer restart when already running"""
        self.timer.start_cyclic(self.callback_function, 0.02)
        time.sleep(0.01)
        initial_count = self.callback_count
        
        # Restart with different timeout
        self.timer.start_cyclic(self.callback_function, 0.01)
        assert self.timer.is_running()
        assert self.timer.is_cyclic()
        
        time.sleep(0.05)
        assert self.callback_count > initial_count
    
    def test_cyclic_timer_set_timeout(self):
        """Test setting timeout for cyclic timer"""
        self.timer.start_cyclic(self.callback_function, 0.02)
        time.sleep(0.01)
        initial_count = self.callback_count
        
        # Change timeout - should take effect in next cycle
        self.timer.set_timeout(0.01)
        assert self.timer.is_running()
        assert self.timer.is_cyclic()
        
        time.sleep(0.05)  # Should trigger with new timeout
        assert self.callback_count > initial_count
    
    def test_cyclic_timer_inheritance(self):
        """Test that CyclicTimer inherits from Timer"""
        assert isinstance(self.timer, Timer)
        
        # Test regular timer functionality on CyclicTimer
        self.timer.start(self.callback_function, 0.01)
        assert self.timer.is_running()
        assert not self.timer.is_cyclic()
        
        time.sleep(0.02)
        assert self.callback_count == 1  # Should only execute once
    
    def test_cyclic_timer_thread_safety(self):
        """Test cyclic timer thread safety"""
        results = []
        
        def worker():
            timer = CyclicTimer()
            count = 0
            
            def callback():
                nonlocal count
                count += 1
                if count >= 2:  # Keep requirement
                    timer.stop()
            
            timer.start_cyclic(callback, 0.01)  # Very short interval
            time.sleep(0.05)  # Much reduced wait time
            results.append(count)
        
        threads = []
        for _ in range(2):  # Keep thread count
            thread = threading.Thread(target=worker)
            threads.append(thread)
            thread.start()
        
        for thread in threads:
            thread.join()
        
        assert len(results) == 2
        assert all(count >= 2 for count in results)
    
    def test_cyclic_timer_callback_exception(self):
        """Test cyclic timer behavior when callback raises exception"""
        def failing_callback():
            raise ValueError("Test exception")
        
        # Timer should stop when callback raises exception
        self.timer.start_cyclic(failing_callback, 0.01)
        time.sleep(0.05)  # Wait to ensure callback has executed
        
        # Timer should be stopped after exception
        assert not self.timer.is_running()
        assert not self.timer.is_cyclic()
    
    def test_cyclic_timer_zero_timeout(self):
        """Test cyclic timer with zero timeout"""
        self.timer.start_cyclic(self.callback_function, 0)
        time.sleep(0.01)  # Small delay
        assert self.callback_count > 0


class TestTimerIntegration:
    """Integration tests for Timer and CyclicTimer"""
    
    def test_multiple_timers_concurrent(self):
        """Test multiple timers running concurrently"""
        timers = [Timer() for _ in range(3)]
        results = [False] * 3
        
        def callback(index):
            results[index] = True
        
        # Start all timers
        for i, timer in enumerate(timers):
            timer.start(callback, 0.01, i)
        
        time.sleep(0.05)  # Wait to ensure all callbacks execute
        
        # Check all callbacks were called
        assert all(results)
        
        # Cleanup
        for timer in timers:
            timer.stop()
    
    def test_timer_and_cyclic_timer_mixed(self):
        """Test mixing regular and cyclic timers"""
        regular_timer = Timer()
        cyclic_timer = CyclicTimer()
        
        regular_count = 0
        cyclic_count = 0
        
        def regular_callback():
            nonlocal regular_count
            regular_count += 1
        
        def cyclic_callback():
            nonlocal cyclic_count
            cyclic_count += 1
            if cyclic_count >= 2:  # Keep requirement
                cyclic_timer.stop()
        
        # Start both timers
        regular_timer.start(regular_callback, 0.01)
        cyclic_timer.start_cyclic(cyclic_callback, 0.01)
        
        time.sleep(0.05)  # Much reduced wait time
        
        # Regular timer should execute once, cyclic should execute multiple times
        assert regular_count == 1
        assert cyclic_count >= 2
        
        # Cleanup
        regular_timer.stop()
        cyclic_timer.stop()
    
    def test_timer_memory_cleanup(self):
        """Test that timers properly clean up resources"""
        timer = Timer()
        timer.start(lambda: None, 0.01)
        
        # Wait for completion
        time.sleep(0.02)
        
        # Timer should be stopped and cleaned up
        assert not timer.is_running()
        assert timer._timer is None
